package r3.spring.bean;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import r3.cluster.loadbalance.LoadBalance;
import r3.cluster.loadbalance.LoadBalanceFactory;
import r3.cluster.router.DefaultRouter;
import r3.cluster.router.Router;
import r3.cluster.router.RouterEntity;
import r3.common.R3Url;
import r3.flow.InHandler;
import r3.flow.OutHandler;
import r3.flow.annotation.In;
import r3.flow.annotation.Out;
import r3.rpc.RpcResponse;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;

/**
 * LeaderBean
 * 代理工厂
 * @author zhoufn
 * @create 2017-12-26 14:48
 **/
public class LeaderBean implements MethodInterceptor {

    /**
     * 负载策略
     */
    private String loadBalance;

    /**
     * 代理接口
     */
    private String interfce;

    public LeaderBean(String interfce, String loadBalance) {
        this.interfce = interfce;
        this.loadBalance = loadBalance;
    }

    /**
     * 创建指定接口的代理类
     * @param interfce
     * @return
     * @throws Exception
     */
    public static Object createLeader(String interfce,String loadBalance) throws Exception{
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Class.forName(interfce));
        enhancer.setCallback(new LeaderBean(interfce, loadBalance));
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        if ("toString".equalsIgnoreCase(method.getName()) || "hashCode".equalsIgnoreCase(method.getName())) {
            return methodProxy.invokeSuper(o, objects);
        }else{
            if(!this.checkMethod(method)){
                throw new Exception("not in or out annotation be found for method " + method.getName());
            }
            List<R3Url> urls = this.discover();
            List<RouterEntity> entities = this.split(method,objects,urls);
            Router router = new DefaultRouter();
            List<RpcResponse> rpcResponses = router.route(entities);
            return this.join(method,rpcResponses);
        }
    }


    /**
     * 返回参数合流
     * @param method
     * @param rpcResponses
     * @return
     * @throws Throwable
     */
    private Object join(Method method,List<RpcResponse> rpcResponses) throws Throwable{
        Class inClass = method.getAnnotation(In.class).value();
        InHandler inHandler = (InHandler)inClass.newInstance();
        Object[] objects = new Object[rpcResponses.size()];
        for(int i=0,l=objects.length;i<l;i++){
            objects[i] = rpcResponses.get(i).getValue();
        }
        return inHandler.join(objects);
    }

    /**
     * 参数分流
     * @param method
     * @param objects
     * @param urls
     * @return
     * @throws Throwable
     */
    private List<RouterEntity> split(Method method,Object[] objects,List<R3Url> urls) throws Throwable{
        Class outClass = method.getAnnotation(Out.class).value();
        OutHandler outHandler = (OutHandler) outClass.newInstance();
        return outHandler.out(method,objects,urls);
    }

    /**
     * 获取可用服务，使用负载均衡
     *
     * @return
     * @throws Exception
     */
    private List<R3Url> discover() throws Exception {
        RegistryBean registryBean = ApplicationBean.getContext().getBean(RegistryBean.class);
        HashMap<String, HashMap<String, R3Url>> workers = registryBean.getRegistryCenter().discover(this.interfce);
        LoadBalance loadBalance = LoadBalanceFactory.creatLoadBalance(this.loadBalance);
        return loadBalance.select(workers);
    }

    /**
     * 校验方法是否配置了In Out注解
     * @param method
     * @return
     * @throws Exception
     */
    private boolean checkMethod(Method method) throws Exception{
        if(method.getParameters() != null && method.getParameters().length > 0){
            Out out = method.getAnnotation(Out.class);
            if(out == null){
                return false;
            }
        }else if(!method.getReturnType().equals(void.class)){
            In in = method.getAnnotation(In.class);
            if(in == null){
                return false;
            }
        }
        return true;
    }

}
